/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.felipecsl.asymmetricgridview;

import ohos.agp.colors.RgbColor;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.DirectionalLayout;
import ohos.agp.components.ListContainer;
import ohos.agp.components.element.ShapeElement;
import ohos.app.Context;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 适配器实现类
 *
 * @since 2021-04-02
 */
final class AdapterImpl implements Component.ClickedListener, Component.LongClickedListener {
    private static final String TAG = "AdapterImpl";
    private final Map<Integer, RowInfo> itemsPerRow = new HashMap<>();
    private final ObjectPool<DirectionalLayout> linearLayoutPool;
    private final Map<Integer, ObjectPool<AsymmetricViewHolder<?>>> viewHoldersMap = new HashMap<>();
    private final Context context;
    private final AgvBaseAdapter<?> agvAdapter;
    private final AsymmetricView listView;
    private final boolean isDebugEnabled;
    private final int number = 20;
    private final int red = 131;
    private final int green = 242;
    private final int blue = 123;
    private final int margin = 2;

    AdapterImpl(Context context, AgvBaseAdapter<?> agvAdapter, AsymmetricView listView) {
        this.context = context;
        this.agvAdapter = agvAdapter;
        this.listView = listView;
        this.isDebugEnabled = listView.isDebugging();
        this.linearLayoutPool = new ObjectPool<>(new LinearLayoutPoolObjectFactory(context));
    }

    private RowInfo calculateItemsForRow(List<RowItem> items) {
        return calculateItemsForRow(items, listView.getNumColumns());
    }

    private RowInfo calculateItemsForRow(List<RowItem> items, float initialSpaceLeft) {
        final List<RowItem> itemsThatFit = new ArrayList<>();
        int currentItem = 0;
        int rowHeight = 1;
        float areaLeft = initialSpaceLeft;

        while (areaLeft > 0 && currentItem < items.size()) {
            final RowItem item = items.get(currentItem++);
            float itemArea = item.getItem().getRowSpan() * item.getItem().getColumnSpan();

            if (isDebugEnabled) {
                LogUtil.info(TAG, String.format("item %s in row with height %s consumes %s area", item,
                    rowHeight, itemArea));
            }

            if (rowHeight < item.getItem().getRowSpan()) {
                // restart with double height
                itemsThatFit.clear();
                rowHeight = item.getItem().getRowSpan();
                currentItem = 0;
                areaLeft = initialSpaceLeft * item.getItem().getRowSpan();
            } else if (areaLeft >= itemArea) {
                areaLeft -= itemArea;
                itemsThatFit.add(item);
            } else if (!listView.isAllowReordering()) {
                break;
            }
        }

        return new RowInfo(rowHeight, itemsThatFit, areaLeft);
    }

    int getRowCount() {
        return itemsPerRow.size();
    }

    void recalculateItemsPerRow() {
        linearLayoutPool.clear();
        itemsPerRow.clear();
        HiExecutor.runWorkThread(() -> {
            List<RowItem> itemsToAdd = new ArrayList<>();
            for (int i = 0; i < agvAdapter.getActualItemCount(); i++) {
                itemsToAdd.add(new RowItem(i, agvAdapter.getItem(i)));
            }
            List<RowInfo> rows = calculateItemsPerRow(itemsToAdd);
            HiExecutor.runMainThread(new Runnable() {
                @Override
                public void run() {
                    for (RowInfo row : rows) {
                        itemsPerRow.put(getRowCount(), row);
                    }

                    if (isDebugEnabled) {
                        for (Map.Entry<Integer, RowInfo> entry : itemsPerRow.entrySet()) {
                            LogUtil.debug(TAG, "row: " + entry.getKey() + ", items: " + entry.getValue().getItems().size());
                        }
                    }
                    LogUtil.info("getCount", "itemsPerRow size   " + itemsPerRow.size());

                    agvAdapter.notifyDataSetChanged();
                }
            });
        });
    }

    void onBindViewHolder(ViewHolder holder, int position, ComponentContainer parent) {
        RowInfo rowInfo = itemsPerRow.get(position);
        if (rowInfo != null) {
            List<RowItem> rowItems = new ArrayList<>(rowInfo.getItems());
            DirectionalLayout layout = initializeLayout(holder.itemView());
            int columnIndex = 0;
            int currentIndex = 0;
            int spaceLeftInColumn = rowInfo.getRowHeight();
            while (!rowItems.isEmpty() && columnIndex < listView.getNumColumns()) {
                RowItem currentItem = rowItems.get(currentIndex);
                if (spaceLeftInColumn == 0) {
                    columnIndex++;
                    currentIndex = 0;
                    spaceLeftInColumn = rowInfo.getRowHeight();
                    continue;
                }
                if (spaceLeftInColumn >= currentItem.getItem().getRowSpan()) {
                    rowItems.remove(currentItem);
                    int actualIndex = currentItem.getIndex();
                    int viewType = agvAdapter.getItemViewType(actualIndex);
                    ObjectPool<AsymmetricViewHolder<?>> pool = viewHoldersMap.get(viewType);
                    if (pool == null) {
                        pool = new ObjectPool<>();
                        viewHoldersMap.put(viewType, pool);
                    }
                    AsymmetricViewHolder viewHolder = pool.get();
                    if (viewHolder == null) {
                        viewHolder = agvAdapter.onCreateAsymmetricViewHolder(actualIndex, parent, viewType);
                    }
                    agvAdapter.onBindAsymmetricViewHolder(viewHolder, parent, actualIndex);
                    Component view = viewHolder.itemComponent;
                    view.setTag(new ViewState(viewType, currentItem, viewHolder));
                    view.setLongClickedListener(this);
                    spaceLeftInColumn -= currentItem.getItem().getRowSpan();
                    currentIndex = 0;
                    view.setLayoutConfig(new DirectionalLayout.LayoutConfig(getRowWidth(currentItem.getItem()),
                        getRowHeight(currentItem.getItem())));
                    DirectionalLayout childLayout = findOrInitializeChildLayout(layout, columnIndex);
                    view.setMarginRight(Utils.vpToPx(view.getContext(), margin));
                    view.setMarginBottom(Utils.vpToPx(view.getContext(), margin));
                    childLayout.addComponent(view);
                } else if (currentIndex < rowItems.size() - 1) {
                    currentIndex++;
                } else {
                    break;
                }
            }
            log(position);
        }
    }

    private void log(int position) {
        if (isDebugEnabled && position % number == 0) {
            LogUtil.debug(TAG, linearLayoutPool.getStats("LinearLayout"));
            for (Map.Entry<Integer, ObjectPool<AsymmetricViewHolder<?>>> e : viewHoldersMap.entrySet()) {
                LogUtil.debug(TAG, e.getValue().getStats("ConvertViewMap, viewType=" + e.getKey()));
            }
        }
    }

    ViewHolder onCreateViewHolder() {
        if (isDebugEnabled) {
            LogUtil.debug(TAG, "onCreateViewHolder()");
        }

        DirectionalLayout layout = new DirectionalLayout(context, null);
        layout.setOrientation(DirectionalLayout.HORIZONTAL);
        if (isDebugEnabled) {
            RgbColor rgbColor = new RgbColor();
            rgbColor.setRed(red);
            rgbColor.setGreen(green);
            rgbColor.setBlue(blue);
            ShapeElement shapeElement = new ShapeElement();
            shapeElement.setRgbColor(rgbColor);
            layout.setBackground(shapeElement);
        }

        layout.setLayoutConfig(new ListContainer.LayoutConfig(
            ListContainer.LayoutConfig.MATCH_PARENT,
            ListContainer.LayoutConfig.MATCH_CONTENT));
        return new ViewHolder(layout);
    }

    int getRowHeight(AsymmetricItem item) {
        return getRowHeight(item.getRowSpan());
    }

    int getRowHeight(int rowSpan) {
        final int rowHeight = listView.getColumnWidth() * rowSpan;
        return rowHeight + ((rowSpan - 1) * listView.getDividerHeight());
    }

    int getRowWidth(AsymmetricItem item) {
        return getRowWidth(item.getColumnSpan());
    }

    protected int getRowWidth(int columnSpan) {
        final int rowWidth = listView.getColumnWidth() * columnSpan;
        return Math.min(rowWidth + ((columnSpan - 1) * listView.getRequestedHorizontalSpacing()),
            Utils.getScreenWidth(context));
    }

    private DirectionalLayout initializeLayout(DirectionalLayout layout) {
        // Clear all layout children before starting
        int childCount = layout.getChildCount();
        for (int j = 0; j < childCount; j++) {
            DirectionalLayout tempChild = (DirectionalLayout) layout.getComponentAt(j);
            linearLayoutPool.put(tempChild);
            int innerChildCount = tempChild.getChildCount();
            for (int k = 0; k < innerChildCount; k++) {
                Component innerView = tempChild.getComponentAt(k);
                ViewState viewState = (ViewState) innerView.getTag();
                ObjectPool<AsymmetricViewHolder<?>> pool = viewHoldersMap.get(viewState.viewType);
                pool.put(viewState.viewHolder);
            }
            tempChild.removeAllComponents();
        }
        layout.removeAllComponents();

        return layout;
    }

    private DirectionalLayout findOrInitializeChildLayout(DirectionalLayout parentLayout, int childIndex) {
        DirectionalLayout childLayout = (DirectionalLayout) parentLayout.getComponentAt(childIndex);

        if (childLayout == null) {
            childLayout = linearLayoutPool.get();
            childLayout.setOrientation(DirectionalLayout.VERTICAL);

            if (isDebugEnabled) {
                RgbColor rgbColor = new RgbColor();
                rgbColor.setRed(red);
                rgbColor.setGreen(green);
                rgbColor.setBlue(blue);
                ShapeElement shapeElement = new ShapeElement();
                shapeElement.setRgbColor(rgbColor);
                childLayout.setBackground(shapeElement);
            }
            childLayout.setLayoutConfig(new ListContainer.LayoutConfig(
                ListContainer.LayoutConfig.MATCH_CONTENT,
                ListContainer.LayoutConfig.MATCH_PARENT));
            childLayout.setMarginRight(Utils.vpToPx(childLayout.getContext(), margin));
            childLayout.setMarginBottom(Utils.vpToPx(childLayout.getContext(), margin));
            parentLayout.addComponent(childLayout);
        }
        return childLayout;
    }

    @Override
    public void onClick(Component component) {
        // noinspection unchecked
        ViewState rowItem = (ViewState) component.getTag();
        listView.fireOnItemClick(rowItem.rowItem.getIndex(), component);
        HiExecutor.runWorkThread(() -> {
            List<RowItem> itemsToAdd = new ArrayList<>();
            for (int i = 0; i < agvAdapter.getActualItemCount(); i++) {
                itemsToAdd.add(new RowItem(i, agvAdapter.getItem(i)));
            }
            List<RowInfo> rows = calculateItemsPerRow(itemsToAdd);
            HiExecutor.runMainThread(() -> {
                for (RowInfo row : rows) {
                    itemsPerRow.put(getRowCount(), row);
                }

                if (isDebugEnabled) {
                    for (Map.Entry<Integer, RowInfo> entry : itemsPerRow.entrySet()) {
                        LogUtil.debug(TAG, "row: " + entry.getKey() + ", items: " + entry.getValue().getItems().size());
                    }
                }
                agvAdapter.notifyDataSetChanged();
            });
        });
    }

    private List<RowInfo> calculateItemsPerRow(List<RowItem> itemsToAdd) {
        List<RowInfo> rows = new ArrayList<>();
        while (!itemsToAdd.isEmpty()) {
            RowInfo stuffThatFit = calculateItemsForRow(itemsToAdd);
            List<RowItem> itemsThatFit = stuffThatFit.getItems();

            if (itemsThatFit.isEmpty()) {
                break;
            }

            for (RowItem entry : itemsThatFit) {
                itemsToAdd.remove(entry);
            }

            rows.add(stuffThatFit);
        }

        return rows;
    }

    @Override
    public void onLongClicked(Component component) {
        // noinspection unchecked
        ViewState rowItem = (ViewState) component.getTag();
        listView.fireOnItemLongClick(rowItem.rowItem.getIndex(), component);
    }

    /**
     * View状态信息类
     *
     * @since 2021-04-02
     */
    private static class ViewState {
        private final int viewType;
        private final RowItem rowItem;
        private final AsymmetricViewHolder<?> viewHolder;

        private ViewState(int viewType, RowItem rowItem, AsymmetricViewHolder<?> viewHolder) {
            this.viewType = viewType;
            this.rowItem = rowItem;
            this.viewHolder = viewHolder;
        }
    }

    /**
     * View复用类
     *
     * @since 2021-04-02
     */
    static class ViewHolder extends BaseViewHolder {
        ViewHolder(DirectionalLayout itemView) {
            super(itemView);
        }

        DirectionalLayout itemView() {
            return (DirectionalLayout) itemComponent;
        }
    }
}
